/* * eID Applet Project. * Copyright (C) 2008-2010 FedICT. * Copyright (C) 2009-2016 e-Contract.be BVBA. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version * 3.0 as published by the Free Software Foundation. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, see * http://www.gnu.org/licenses/. */ package be.fedict.eid.applet; import java.awt.Component; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.CookieHandler; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Observable; import java.util.Observer; import java.util.StringTokenizer; import javax.security.auth.login.FailedLoginException; import javax.security.auth.login.LoginException; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import be.fedict.eid.applet.Messages.MESSAGE_ID; import be.fedict.eid.applet.io.AppletSSLSocketFactory; import be.fedict.eid.applet.io.HttpURLConnectionHttpReceiver; import be.fedict.eid.applet.io.HttpURLConnectionHttpTransmitter; import be.fedict.eid.applet.io.LocalAppletProtocolContext; import be.fedict.eid.applet.sc.PcscEid; import be.fedict.eid.applet.sc.Task; import be.fedict.eid.applet.sc.TaskRunner; import be.fedict.eid.applet.shared.AdministrationMessage; import be.fedict.eid.applet.shared.AppletProtocolMessageCatalog; import be.fedict.eid.applet.shared.AuthSignRequestMessage; import be.fedict.eid.applet.shared.AuthSignResponseMessage; import be.fedict.eid.applet.shared.AuthenticationContract; import be.fedict.eid.applet.shared.AuthenticationDataMessage; import be.fedict.eid.applet.shared.AuthenticationRequestMessage; import be.fedict.eid.applet.shared.CheckClientMessage; import be.fedict.eid.applet.shared.ClientEnvironmentMessage; import be.fedict.eid.applet.shared.ContinueInsecureMessage; import be.fedict.eid.applet.shared.ErrorCode; import be.fedict.eid.applet.shared.FileDigestsDataMessage; import be.fedict.eid.applet.shared.FilesDigestRequestMessage; import be.fedict.eid.applet.shared.FinishedMessage; import be.fedict.eid.applet.shared.HelloMessage; import be.fedict.eid.applet.shared.IdentificationRequestMessage; import be.fedict.eid.applet.shared.IdentityDataMessage; import be.fedict.eid.applet.shared.InsecureClientMessage; import be.fedict.eid.applet.shared.SignCertificatesDataMessage; import be.fedict.eid.applet.shared.SignCertificatesRequestMessage; import be.fedict.eid.applet.shared.SignRequestMessage; import be.fedict.eid.applet.shared.SignatureDataMessage; import be.fedict.eid.applet.shared.annotation.ResponsesAllowed; import be.fedict.eid.applet.shared.protocol.ProtocolContext; import be.fedict.eid.applet.shared.protocol.ProtocolStateMachine; import be.fedict.eid.applet.shared.protocol.Transport; import be.fedict.eid.applet.shared.protocol.Unmarshaller; /** * Controller component. Contains the eID logic. Interacts with {@link View} and * {@link Runtime} for outside world communication. * * @author Frank Cornelis * */ public class Controller { private final View view; private final Runtime runtime; private final Messages messages; private final PcscEid pcscEidSpi; private final ProtocolStateMachine protocolStateMachine; public Controller(View view, Runtime runtime, Messages messages) { this.runtime = runtime; this.messages = messages; macosxSandboxDetection(view); try { this.pcscEidSpi = new PcscEid(view, this.messages); } catch (Exception e) { String msg = "error loading PC/SC eID component: " + e.getMessage(); view.addDetailMessage(msg); throw new RuntimeException(msg); } String ppduNames = runtime.getParameter("PPDUNames"); if (null != ppduNames) { StringTokenizer stringTokenizer = new StringTokenizer(ppduNames, ","); while (stringTokenizer.hasMoreTokens()) { String ppduName = stringTokenizer.nextToken(); view.addDetailMessage("PPDU name: " + ppduName.toLowerCase()); this.pcscEidSpi.addPPDUName(ppduName); } } this.pcscEidSpi.addObserver(new PcscEidObserver()); ProtocolContext protocolContext = new LocalAppletProtocolContext(view); this.protocolStateMachine = new ProtocolStateMachine(protocolContext); this.view = new ExclusiveAccessViewDecorator(view, this.pcscEidSpi); } private int getBrowserMajorVersion(String userAgent, String prefix) { String majorVersionStr = userAgent.substring(userAgent.indexOf(prefix) + prefix.length()); majorVersionStr = majorVersionStr.substring(0, majorVersionStr.indexOf(".")); Integer majorVersion = Integer.parseInt(majorVersionStr); return majorVersion; } private boolean isSafari10(String userAgent) { if (null == userAgent) { return false; } int majorVersion = getBrowserMajorVersion(userAgent, "Version/"); if (majorVersion >= 10) { return true; } return false; } private void macosxSandboxDetection(View view) { String osName = System.getProperty("os.name"); if (osName.equals("Mac OS X")) { boolean sandboxed = System.getenv("DIRHELPER_USER_DIR_SUFFIX") != null; if (sandboxed) { String userAgent = this.runtime.getParameter("UserAgent"); String safariMessage; if (isSafari10(userAgent)) { safariMessage = this.messages.getMessage(MESSAGE_ID.SAFARI_SANDBOX_1); safariMessage += this.messages.getMessage(MESSAGE_ID.SAFARI_SANDBOX_10); safariMessage += this.runtime.getDocumentBase().getHost() + " "; safariMessage += this.messages.getMessage(MESSAGE_ID.SAFARI_SANDBOX_2); } else { safariMessage = this.messages.getMessage(MESSAGE_ID.SAFARI_SANDBOX_1); safariMessage += this.runtime.getDocumentBase().getHost() + " "; safariMessage += this.messages.getMessage(MESSAGE_ID.SAFARI_SANDBOX_2); } JOptionPane.showMessageDialog(view.getParentComponent(), safariMessage, "Safari Java Sandbox", JOptionPane.WARNING_MESSAGE); } } } private <T> T sendMessage(Object message, Class<T> responseClass) throws MalformedURLException, IOException { Object responseObject = sendMessage(message); if (false == responseClass.equals(responseObject.getClass())) { throw new RuntimeException("response message not of type: " + responseClass.getName()); } @SuppressWarnings("unchecked") T response = (T) responseObject; return response; } private Object sendMessage(Object message) throws MalformedURLException, IOException { addDetailMessage("sending message: " + message.getClass().getSimpleName()); Class<?> messageClass = message.getClass(); ResponsesAllowed responsesAllowedAnnotation = messageClass.getAnnotation(ResponsesAllowed.class); if (null == responsesAllowedAnnotation) { throw new RuntimeException("message should have a @ResponsesAllowed constraint"); } this.protocolStateMachine.checkRequestMessage(message); String userAgent = this.runtime.getParameter("UserAgent"); boolean noChunkedTransferEncoding = false; String noChunkedTransferEncodingParam = this.runtime.getParameter("NoChunkedTransferEncoding"); if (null != noChunkedTransferEncodingParam) { noChunkedTransferEncoding = Boolean.parseBoolean(noChunkedTransferEncodingParam); addDetailMessage("no chunked transfer-encoding: " + noChunkedTransferEncoding); } HttpURLConnection connection = getServerConnection(); HttpURLConnectionHttpTransmitter httpTransmitter = new HttpURLConnectionHttpTransmitter(connection, userAgent, noChunkedTransferEncoding); Transport.transfer(message, httpTransmitter); int responseCode = connection.getResponseCode(); if (HttpURLConnection.HTTP_OK != responseCode) { String msg; if (HttpURLConnection.HTTP_NOT_FOUND == responseCode) { msg = "HTTP NOT FOUND! eID Applet Service not running?"; } else { msg = Integer.toString(responseCode); } this.view.addDetailMessage("HTTP response code: " + msg); printHttpResponseContent(connection); throw new IOException("error sending message to service. HTTP status code: " + msg); } Unmarshaller unmarshaller = new Unmarshaller(new AppletProtocolMessageCatalog()); HttpURLConnectionHttpReceiver httpReceiver = new HttpURLConnectionHttpReceiver(connection); Object responseObject = unmarshaller.receive(httpReceiver); Class<?>[] responsesAllowed = responsesAllowedAnnotation.value(); if (false == isOfClass(responseObject, responsesAllowed)) { throw new RuntimeException("response not of correct type: " + responseObject.getClass()); } addDetailMessage("response message: " + responseObject.getClass().getSimpleName()); this.protocolStateMachine.checkResponseMessage(responseObject); return responseObject; } private void printHttpResponseContent(HttpURLConnection connection) { InputStream errorStream = connection.getErrorStream(); if (null == errorStream) { return; } BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream)); String line; try { while (null != (line = reader.readLine())) { this.view.addDetailMessage(line); } } catch (IOException e) { this.view.addDetailMessage("I/O error: " + e.getMessage()); } } private boolean isOfClass(Object object, Class<?>[] classes) { for (Class<?> clazz : classes) { if (clazz.equals(object.getClass())) { return true; } } return false; } public Object run() { printEnvironment(); try { Applet applet = this.runtime.getApplet(); String language = applet.getParameter(Applet.LANGUAGE_PARAM); HelloMessage helloMessage = new HelloMessage(language); Object resultMessage = sendMessage(helloMessage); if (resultMessage instanceof CheckClientMessage) { addDetailMessage("Need to check the client secure environment..."); ClientEnvironmentMessage clientEnvMessage = new ClientEnvironmentMessage(); clientEnvMessage.javaVersion = System.getProperty("java.version"); clientEnvMessage.javaVendor = System.getProperty("java.vendor"); clientEnvMessage.osName = System.getProperty("os.name"); clientEnvMessage.osArch = System.getProperty("os.arch"); clientEnvMessage.osVersion = System.getProperty("os.version"); clientEnvMessage.readerList = this.pcscEidSpi.getReaderList(); clientEnvMessage.navigatorAppName = this.runtime.getParameter("NavigatorAppName"); clientEnvMessage.navigatorAppVersion = this.runtime.getParameter("NavigatorAppVersion"); clientEnvMessage.navigatorUserAgent = this.runtime.getParameter("NavigatorUserAgent"); resultMessage = sendMessage(clientEnvMessage); if (resultMessage instanceof InsecureClientMessage) { InsecureClientMessage insecureClientMessage = (InsecureClientMessage) resultMessage; if (insecureClientMessage.warnOnly) { int result = JOptionPane.showConfirmDialog(this.view.getParentComponent(), "Your system has been marked as insecure client environment.\n" + "Do you want to continue the eID operation?", "Insecure Client Environment", JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); if (JOptionPane.OK_OPTION != result) { setStatusMessage(Status.ERROR, MESSAGE_ID.SECURITY_ERROR); addDetailMessage("insecure client environment"); return null; } resultMessage = sendMessage(new ContinueInsecureMessage()); } else { JOptionPane.showMessageDialog(this.view.getParentComponent(), "Your system has been marked as insecure client environment.", "Insecure Client Environment", JOptionPane.ERROR_MESSAGE); setStatusMessage(Status.ERROR, MESSAGE_ID.SECURITY_ERROR); addDetailMessage("received an insecure client environment message"); return null; } } } if (resultMessage instanceof AdministrationMessage) { AdministrationMessage administrationMessage = (AdministrationMessage) resultMessage; boolean changePin = administrationMessage.changePin; boolean unblockPin = administrationMessage.unblockPin; boolean removeCard = administrationMessage.removeCard; boolean logoff = administrationMessage.logoff; boolean requireSecureReader = administrationMessage.requireSecureReader; addDetailMessage("change pin: " + changePin); addDetailMessage("unblock pin: " + unblockPin); addDetailMessage("remove card: " + removeCard); addDetailMessage("logoff: " + logoff); addDetailMessage("require secure reader: " + requireSecureReader); administration(unblockPin, changePin, logoff, removeCard, requireSecureReader); } if (resultMessage instanceof FilesDigestRequestMessage) { FilesDigestRequestMessage filesDigestRequestMessage = (FilesDigestRequestMessage) resultMessage; resultMessage = performFilesDigestOperation(filesDigestRequestMessage.digestAlgo); } if (resultMessage instanceof SignCertificatesRequestMessage) { SignCertificatesRequestMessage signCertificatesRequestMessage = (SignCertificatesRequestMessage) resultMessage; SignCertificatesDataMessage signCertificatesDataMessage = performSignCertificatesOperation( signCertificatesRequestMessage); resultMessage = sendMessage(signCertificatesDataMessage); } if (resultMessage instanceof SignRequestMessage) { SignRequestMessage signRequestMessage = (SignRequestMessage) resultMessage; resultMessage = performEidSignOperation(signRequestMessage); } if (resultMessage instanceof AuthenticationRequestMessage) { AuthenticationRequestMessage authnRequest = (AuthenticationRequestMessage) resultMessage; resultMessage = performEidAuthnOperation(authnRequest); } if (resultMessage instanceof AuthSignRequestMessage) { AuthSignRequestMessage authSignRequestMessage = (AuthSignRequestMessage) resultMessage; resultMessage = performAuthnSignOperation(authSignRequestMessage); } if (resultMessage instanceof IdentificationRequestMessage) { IdentificationRequestMessage identificationRequestMessage = (IdentificationRequestMessage) resultMessage; addDetailMessage("include address: " + identificationRequestMessage.includeAddress); addDetailMessage("include photo: " + identificationRequestMessage.includePhoto); addDetailMessage("include integrity data: " + identificationRequestMessage.includeIntegrityData); addDetailMessage("include certificates: " + identificationRequestMessage.includeCertificates); addDetailMessage("remove card: " + identificationRequestMessage.removeCard); addDetailMessage("identity data usage: " + identificationRequestMessage.identityDataUsage); resultMessage = performEidIdentificationOperation(identificationRequestMessage.includeAddress, identificationRequestMessage.includePhoto, identificationRequestMessage.includeIntegrityData, identificationRequestMessage.includeCertificates, identificationRequestMessage.removeCard, identificationRequestMessage.identityDataUsage); } if (resultMessage instanceof FinishedMessage) { FinishedMessage finishedMessage = (FinishedMessage) resultMessage; if (null != finishedMessage.errorCode) { switch (finishedMessage.errorCode) { case CERTIFICATE: addDetailMessage("something wrong with your certificate"); setStatusMessage(Status.ERROR, MESSAGE_ID.SECURITY_ERROR); return null; case CERTIFICATE_EXPIRED: setStatusMessage(Status.ERROR, MESSAGE_ID.CERTIFICATE_EXPIRED_ERROR); return null; case CERTIFICATE_REVOKED: setStatusMessage(Status.ERROR, MESSAGE_ID.CERTIFICATE_REVOKED_ERROR); return null; case CERTIFICATE_NOT_TRUSTED: setStatusMessage(Status.ERROR, MESSAGE_ID.CERTIFICATE_NOT_TRUSTED); return null; case AUTHORIZATION: setStatusMessage(Status.ERROR, MESSAGE_ID.AUTHORIZATION_ERROR); this.runtime.gotoAuthorizationErrorPage(); return null; default: } setStatusMessage(Status.ERROR, MESSAGE_ID.GENERIC_ERROR); addDetailMessage("error code @ finish: " + finishedMessage.errorCode); return null; } } } catch (SecurityException e) { setStatusMessage(Status.ERROR, MESSAGE_ID.SECURITY_ERROR); addDetailMessage("error: " + e.getMessage()); return null; } catch (Throwable e) { addDetailMessage("error: " + e.getMessage()); addDetailMessage("error type: " + e.getClass().getName()); StackTraceElement[] stackTrace = e.getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { addDetailMessage("at " + stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber()); } Throwable cause = e.getCause(); if (null != cause) { addDetailMessage("Caused by: " + cause.getClass().getName() + ": " + cause.getMessage()); stackTrace = cause.getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { addDetailMessage("at " + stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber()); } /* * Next is specific for the OpenSC PKCS#11 library. */ if (FailedLoginException.class == cause.getClass()) { setStatusMessage(Status.ERROR, MESSAGE_ID.PIN_INCORRECT); return null; } if (LoginException.class == cause.getClass()) { if (null == cause.getMessage()) { /* * This seems to be the case for OpenSC. */ setStatusMessage(Status.ERROR, MESSAGE_ID.PIN_BLOCKED); return null; } setStatusMessage(Status.ERROR, MESSAGE_ID.SECURITY_ERROR); return null; } } /* * We don't refer to javax.smartcardio directly since this code also * need to work an a Java 5 runtime. */ if ("javax.smartcardio.CardException".equals(e.getClass().getName())) { setStatusMessage(Status.ERROR, MESSAGE_ID.CARD_ERROR); addDetailMessage("card error: " + e.getMessage()); return null; } setStatusMessage(Status.ERROR, MESSAGE_ID.GENERIC_ERROR); return null; } setStatusMessage(Status.NORMAL, MESSAGE_ID.DONE); this.runtime.gotoTargetPage(); return null; } private Object performAuthnSignOperation(AuthSignRequestMessage authSignRequestMessage) throws Exception { addDetailMessage("auth sign request..."); setStatusMessage(Status.NORMAL, MESSAGE_ID.DETECTING_CARD); waitForEIdCardPcsc(); setStatusMessage(Status.NORMAL, MESSAGE_ID.AUTHENTICATING); byte[] digestValue = authSignRequestMessage.computedDigestValue; String digestAlgo = authSignRequestMessage.digestAlgo; boolean logoff = authSignRequestMessage.logoff; String stdMsg = this.messages.getMessage(MESSAGE_ID.PROTOCOL_SIGNATURE); String message = stdMsg + "\n" + authSignRequestMessage.message; try { this.view.confirmAuthenticationSignature(message); } catch (Exception e) { this.pcscEidSpi.close(); throw e; } try { byte[] signatureValue = this.pcscEidSpi.sign(digestValue, digestAlgo, PcscEid.AUTHN_KEY_ID, false); if (logoff) { this.pcscEidSpi.logoff(); } AuthSignResponseMessage authSignResponseMessage = new AuthSignResponseMessage(signatureValue); Object responseMessage = sendMessage(authSignResponseMessage); return responseMessage; } finally { this.pcscEidSpi.close(); } } private SignCertificatesDataMessage performSignCertificatesOperation( SignCertificatesRequestMessage signCertificatesRequestMessage) throws Exception { addDetailMessage("performing sign certificates retrieval operation..."); boolean includeIdentity = signCertificatesRequestMessage.includeIdentity; boolean includeAddress = signCertificatesRequestMessage.includeAddress; boolean includePhoto = signCertificatesRequestMessage.includePhoto; boolean includeIntegrityData = signCertificatesRequestMessage.includeIntegrityData; byte[] signCertFile; byte[] citizenCaCertFile; byte[] rootCaCertFile; byte[] identityFile = null; byte[] addressFile = null; byte[] photoFile = null; byte[] identitySignFile = null; byte[] addressSignFile = null; byte[] nrnCertFile = null; setStatusMessage(Status.NORMAL, MESSAGE_ID.DETECTING_CARD); waitForEIdCardPcsc(); try { setStatusMessage(Status.NORMAL, MESSAGE_ID.READING_IDENTITY); if (includeIdentity || includeAddress || includePhoto) { boolean response = this.view.privacyQuestion(includeAddress, includePhoto, null); if (false == response) { this.pcscEidSpi.close(); if (false == this.runtime.gotoCancelPage()) { throw new SecurityException("user did not agree to release eID identity information"); } else { // TODO // return new FinishedMessage(ErrorCode.USER_CANCELED); throw new SecurityException("user did not agree to release eID identity information"); } } // FIXME: repeat for screen reader, perhaps we need pre- and // post-approval msg setStatusMessage(Status.NORMAL, MESSAGE_ID.OK); setStatusMessage(Status.NORMAL, MESSAGE_ID.READING_IDENTITY); } signCertFile = this.pcscEidSpi.readFile(PcscEid.SIGN_CERT_FILE_ID); addDetailMessage("size sign cert file: " + signCertFile.length); citizenCaCertFile = this.pcscEidSpi.readFile(PcscEid.CA_CERT_FILE_ID); addDetailMessage("size citizen CA cert file: " + citizenCaCertFile.length); rootCaCertFile = this.pcscEidSpi.readFile(PcscEid.ROOT_CERT_FILE_ID); addDetailMessage("size root CA cert file: " + rootCaCertFile.length); if (includeIdentity || includeAddress || includePhoto) { if (includeIdentity) { addDetailMessage("reading identity file"); identityFile = this.pcscEidSpi.readFile(PcscEid.IDENTITY_FILE_ID); if (includeIntegrityData) { addDetailMessage("reading identity sign file"); identitySignFile = this.pcscEidSpi.readFile(PcscEid.IDENTITY_SIGN_FILE_ID); } } if (includeAddress) { addDetailMessage("reading address file"); addressFile = this.pcscEidSpi.readFile(PcscEid.ADDRESS_FILE_ID); if (includeIntegrityData) { addDetailMessage("reading address sign file"); addressSignFile = this.pcscEidSpi.readFile(PcscEid.ADDRESS_SIGN_FILE_ID); } } if (includePhoto) { addDetailMessage("reading photo file"); photoFile = this.pcscEidSpi.readFile(PcscEid.PHOTO_FILE_ID); } if (null != identitySignFile || null != addressSignFile) { addDetailMessage("reading NRN certificate file"); nrnCertFile = this.pcscEidSpi.readFile(PcscEid.RRN_CERT_FILE_ID); } } } finally { this.pcscEidSpi.close(); } SignCertificatesDataMessage signCertificatesDataMessage = new SignCertificatesDataMessage(signCertFile, citizenCaCertFile, rootCaCertFile, identityFile, addressFile, photoFile, identitySignFile, addressSignFile, nrnCertFile); return signCertificatesDataMessage; } /** * We're not accepting MD5. */ private final String[] SUPPORTED_FILES_DIGEST_ALGOS = new String[] { "SHA1", "SHA-1", "SHA-256", "SHA-384", "SHA-512" }; private Object performFilesDigestOperation(String filesDigestAlgo) throws NoSuchAlgorithmException, IOException { addDetailMessage("files digest algorithm: " + filesDigestAlgo); boolean isSupportedFilesDigestAlgo = false; for (String supportedFilesDigestAlgo : SUPPORTED_FILES_DIGEST_ALGOS) { if (supportedFilesDigestAlgo.equals(filesDigestAlgo)) { isSupportedFilesDigestAlgo = true; break; } } if (false == isSupportedFilesDigestAlgo) { throw new SecurityException("files digest algo not supported: " + filesDigestAlgo); } MessageDigest messageDigest = MessageDigest.getInstance(filesDigestAlgo); setStatusMessage(Status.NORMAL, MESSAGE_ID.SELECT_FILES); JFileChooser fileChooser = new JFileChooser(); fileChooser.setMultiSelectionEnabled(true); int returnCode = fileChooser.showDialog(getParentComponent(), this.messages.getMessage(MESSAGE_ID.SELECT_FILES)); if (JFileChooser.APPROVE_OPTION != returnCode) { throw new RuntimeException("file selection aborted"); } setStatusMessage(Status.NORMAL, MESSAGE_ID.DIGESTING_FILES); FileDigestsDataMessage fileDigestsDataMessage = new FileDigestsDataMessage(); fileDigestsDataMessage.fileDigestInfos = new LinkedList<String>(); File[] selectedFiles = fileChooser.getSelectedFiles(); long totalSize = 0; for (File selectedFile : selectedFiles) { totalSize += selectedFile.length(); } final int BUFFER_SIZE = 1024 * 10; int progressMax = (int) (totalSize / BUFFER_SIZE); this.view.resetProgress(progressMax); addDetailMessage("total data size to digest: " + (totalSize / 1024) + " KiB"); for (File selectedFile : selectedFiles) { fileDigestsDataMessage.fileDigestInfos.add(filesDigestAlgo); long fileSize = selectedFile.length(); addDetailMessage(selectedFile.getAbsolutePath() + ": " + (fileSize / 1024) + " KiB"); FileInputStream fileInputStream = new FileInputStream(selectedFile); DigestInputStream digestInputStream = new DigestInputStream(fileInputStream, messageDigest); byte[] buffer = new byte[BUFFER_SIZE]; while (-1 != digestInputStream.read(buffer)) { this.view.increaseProgress(); } digestInputStream.close(); byte[] fileDigestValue = messageDigest.digest(); messageDigest.reset(); String fileDigest = toHex(fileDigestValue); fileDigestsDataMessage.fileDigestInfos.add(fileDigest); fileDigestsDataMessage.fileDigestInfos.add(selectedFile.getName()); } this.view.setProgressIndeterminate(); Object resultMessage = sendMessage(fileDigestsDataMessage); return resultMessage; } public static String toHex(byte[] data) { StringBuffer stringBuffer = new StringBuffer(); for (byte b : data) { stringBuffer.append(toHex(b >> 4)); stringBuffer.append(toHex(b)); } return stringBuffer.toString(); } private static char toHex(int value) { value &= 0xf; switch (value) { case 10: return 'A'; case 11: return 'B'; case 12: return 'C'; case 13: return 'D'; case 14: return 'E'; case 15: return 'F'; default: return (char) ('0' + value); } } private FinishedMessage performEidSignOperation(SignRequestMessage signRequestMessage) throws Exception { boolean logoff = signRequestMessage.logoff; boolean removeCard = signRequestMessage.removeCard; boolean requireSecureReader = signRequestMessage.requireSecureReader; addDetailMessage("logoff: " + logoff); addDetailMessage("remove card: " + removeCard); addDetailMessage("require secure smart card reader: " + requireSecureReader); setStatusMessage(Status.NORMAL, MESSAGE_ID.DETECTING_CARD); waitForEIdCardPcsc(); setStatusMessage(Status.NORMAL, MESSAGE_ID.SIGNING); byte[] signatureValue; byte[] signCertFile; byte[] citizenCaCertFile; byte[] rootCaCertFile; try { int response = this.view.confirmSigning(signRequestMessage.description, signRequestMessage.digestAlgo); if (JOptionPane.OK_OPTION != response) { if (false == this.runtime.gotoCancelPage()) { throw new SecurityException("sign operation aborted"); } else { return new FinishedMessage(ErrorCode.USER_CANCELED); } } try { signatureValue = this.pcscEidSpi.sign(signRequestMessage.digestValue, signRequestMessage.digestAlgo, requireSecureReader); } catch (UserCancelledException e) { if (false == this.runtime.gotoCancelPage()) { throw new SecurityException("sign operation aborted"); } else { return new FinishedMessage(ErrorCode.USER_CANCELED); } } int maxProgress = 0; maxProgress += (1050 / 255) + 1; // sign cert file maxProgress += (1050 / 255) + 1; // CA cert file maxProgress += (1050 / 255) + 1; // Root cert file this.view.resetProgress(maxProgress); signCertFile = this.pcscEidSpi.readFile(PcscEid.SIGN_CERT_FILE_ID); citizenCaCertFile = this.pcscEidSpi.readFile(PcscEid.CA_CERT_FILE_ID); rootCaCertFile = this.pcscEidSpi.readFile(PcscEid.ROOT_CERT_FILE_ID); this.view.setProgressIndeterminate(); if (signRequestMessage.logoff && !signRequestMessage.removeCard) { this.pcscEidSpi.logoff(); } if (signRequestMessage.removeCard) { setStatusMessage(Status.NORMAL, MESSAGE_ID.REMOVE_CARD); this.pcscEidSpi.removeCard(); } } finally { this.pcscEidSpi.close(); } SignatureDataMessage signatureDataMessage = new SignatureDataMessage(signatureValue, signCertFile, citizenCaCertFile, rootCaCertFile); Object responseMessage = sendMessage(signatureDataMessage); if (false == (responseMessage instanceof FinishedMessage)) { throw new RuntimeException("finish expected"); } FinishedMessage finishedMessage = (FinishedMessage) responseMessage; return finishedMessage; } private void administration(boolean unblockPin, boolean changePin, boolean logoff, boolean removeCard, boolean requireSecureReader) throws Exception { waitForEIdCardPcsc(); try { if (unblockPin) { setStatusMessage(Status.NORMAL, Messages.MESSAGE_ID.PIN_UNBLOCK); this.pcscEidSpi.unblockPin(requireSecureReader); } if (changePin) { setStatusMessage(Status.NORMAL, Messages.MESSAGE_ID.PIN_CHANGE); this.pcscEidSpi.changePin(requireSecureReader); } if (logoff) { this.pcscEidSpi.logoff(); } if (removeCard) { setStatusMessage(Status.NORMAL, MESSAGE_ID.REMOVE_CARD); this.pcscEidSpi.removeCard(); } } finally { /* * Required to release the exclusive access lock. Else a next * execution of the eID Applet will fail. */ this.pcscEidSpi.close(); } } private Object performEidAuthnOperation(AuthenticationRequestMessage authnRequest) throws Exception { byte[] challenge = authnRequest.challenge; boolean removeCard = authnRequest.removeCard; boolean includeHostname = authnRequest.includeHostname; boolean includeInetAddress = authnRequest.includeInetAddress; boolean logoff = authnRequest.logoff; boolean preLogoff = authnRequest.preLogoff; boolean sessionIdChannelBinding = authnRequest.sessionIdChannelBinding; boolean serverCertificateChannelBinding = authnRequest.serverCertificateChannelBinding; boolean includeIdentity = authnRequest.includeIdentity; boolean includeCertificates = authnRequest.includeCertificates; boolean includeAddress = authnRequest.includeAddress; boolean includePhoto = authnRequest.includePhoto; boolean includeIntegrityData = authnRequest.includeIntegrityData; boolean requireSecureReader = authnRequest.requireSecureReader; String transactionMessage = authnRequest.transactionMessage; if (challenge.length < 20) { throw new SecurityException("challenge should be at least 20 bytes long."); } addDetailMessage("include hostname: " + includeHostname); addDetailMessage("include inet address: " + includeInetAddress); addDetailMessage("remove card after authn: " + removeCard); addDetailMessage("logoff: " + logoff); addDetailMessage("pre-logoff: " + preLogoff); addDetailMessage("TLS session Id channel binding: " + sessionIdChannelBinding); addDetailMessage("server certificate channel binding: " + serverCertificateChannelBinding); addDetailMessage("include identity: " + includeIdentity); addDetailMessage("include certificates: " + includeCertificates); addDetailMessage("include address: " + includeAddress); addDetailMessage("include photo: " + includePhoto); addDetailMessage("include integrity data: " + includeIntegrityData); addDetailMessage("require secure smart card reader: " + requireSecureReader); addDetailMessage("transaction message: " + transactionMessage); String hostname; if (includeHostname) { /* * We extract the hostname from the web page location in which this * eID Applet is embedded. */ URL documentBase = this.runtime.getDocumentBase(); hostname = documentBase.getHost(); addDetailMessage("hostname: " + hostname); } else { hostname = null; } InetAddress inetAddress; if (includeInetAddress) { URL documentBase = this.runtime.getDocumentBase(); inetAddress = InetAddress.getByName(documentBase.getHost()); addDetailMessage("inet address: " + inetAddress.getHostAddress()); } else { inetAddress = null; } byte[] sessionId; if (sessionIdChannelBinding) { sessionId = AppletSSLSocketFactory.getActualSessionId(); } else { sessionId = null; } byte[] encodedServerCertificate; if (serverCertificateChannelBinding) { encodedServerCertificate = AppletSSLSocketFactory.getActualEncodedServerCertificate(); } else { encodedServerCertificate = null; } setStatusMessage(Status.NORMAL, MESSAGE_ID.DETECTING_CARD); waitForEIdCardPcsc(); setStatusMessage(Status.NORMAL, MESSAGE_ID.AUTHENTICATING); byte[] salt = this.pcscEidSpi.getChallenge(20); AuthenticationContract authenticationContract = new AuthenticationContract(salt, hostname, inetAddress, sessionId, encodedServerCertificate, challenge); byte[] toBeSigned = authenticationContract.calculateToBeSigned(); if (includeIdentity || includeAddress || includePhoto) { boolean response = this.view.privacyQuestion(includeAddress, includePhoto, null); if (false == response) { this.pcscEidSpi.close(); if (false == this.runtime.gotoCancelPage()) { throw new SecurityException("user did not agree to release eID identity information"); } else { return new FinishedMessage(ErrorCode.USER_CANCELED); } } } byte[] signatureValue; byte[] identityData = null; byte[] addressData = null; byte[] photoData = null; byte[] identitySignatureData = null; byte[] addressSignatureData = null; byte[] rrnCertData = null; byte[] authnCertFile = null; byte[] signCertFile = null; byte[] citCaCertFile = null; byte[] rootCaCertFile = null; byte[] signedTransactionMessage = null; try { if (preLogoff) { /* * Use the PreLogoff feature to make sure that the user has to * enter his PIN code on each authentication request. */ this.view.addDetailMessage("performing a pre-logoff"); this.pcscEidSpi.logoff(); } try { signatureValue = this.pcscEidSpi.signAuthn(toBeSigned, requireSecureReader); } catch (UserCancelledException e) { if (false == this.runtime.gotoCancelPage()) { throw e; } else { return new FinishedMessage(ErrorCode.USER_CANCELED); } } if (null != transactionMessage) { signedTransactionMessage = this.pcscEidSpi.signTransactionMessage(transactionMessage, requireSecureReader); } int maxProgress = 0; maxProgress += (1050 / 255) + 1; // authn cert file maxProgress += (1050 / 255) + 1; // CA cert file maxProgress += (1050 / 255) + 1; // Root cert file if (includeIdentity) { maxProgress++; } if (includeAddress) { maxProgress++; } if (includePhoto) { maxProgress += 3000 / 255; } if (includeIntegrityData) { if (includeIdentity) { maxProgress++; // identity signature file } if (includeAddress) { maxProgress++; // address signature file } maxProgress += (1050 / 255) + 1; // RRN certificate file } this.view.resetProgress(maxProgress); /* * Next design pattern is the only way to handle the case where * multiple application access the smart card at the same time. */ TaskRunner taskRunner = new TaskRunner(this.pcscEidSpi, this.view); authnCertFile = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.AUTHN_CERT_FILE_ID); } }); citCaCertFile = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.CA_CERT_FILE_ID); } }); rootCaCertFile = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.ROOT_CERT_FILE_ID); } }); if (includeCertificates) { addDetailMessage("reading sign certificate file..."); signCertFile = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.SIGN_CERT_FILE_ID); } }); addDetailMessage("size non-repud cert file: " + signCertFile.length); } if (includeIdentity || includeAddress || includePhoto) { setStatusMessage(Status.NORMAL, MESSAGE_ID.READING_IDENTITY); } if (includeIdentity) { identityData = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.IDENTITY_FILE_ID); } }); } if (includeAddress) { addressData = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.ADDRESS_FILE_ID); } }); } if (includePhoto) { photoData = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.PHOTO_FILE_ID); } }); } if (includeIntegrityData) { if (includeIdentity) { identitySignatureData = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.IDENTITY_SIGN_FILE_ID); } }); } if (includeAddress) { addressSignatureData = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.ADDRESS_SIGN_FILE_ID); } }); } rrnCertData = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.RRN_CERT_FILE_ID); } }); } this.view.setProgressIndeterminate(); if (logoff && !removeCard) { this.pcscEidSpi.logoff(); } if (removeCard) { setStatusMessage(Status.NORMAL, MESSAGE_ID.REMOVE_CARD); this.pcscEidSpi.removeCard(); } } finally { this.pcscEidSpi.close(); } AuthenticationDataMessage authenticationDataMessage = new AuthenticationDataMessage(salt, sessionId, signatureValue, authnCertFile, citCaCertFile, rootCaCertFile, signCertFile, identityData, addressData, photoData, identitySignatureData, addressSignatureData, rrnCertData, encodedServerCertificate, signedTransactionMessage); Object responseMessage = sendMessage(authenticationDataMessage); return responseMessage; } private void printEnvironment() { Version version = new Version(); addDetailMessage("eID browser applet version: " + version.getVersion()); addDetailMessage("Java version: " + System.getProperty("java.version")); addDetailMessage("Java vendor: " + System.getProperty("java.vendor")); addDetailMessage("OS: " + System.getProperty("os.name")); addDetailMessage("OS version: " + System.getProperty("os.version")); addDetailMessage("OS arch: " + System.getProperty("os.arch")); addDetailMessage("Web application URL: " + this.runtime.getDocumentBase()); addDetailMessage("Current time: " + new Date()); /* * Next we check for the presence of the session cookie. */ CookieHandler cookieHandler = CookieHandler.getDefault(); if (null != cookieHandler) { URL documentBase = this.runtime.getApplet().getDocumentBase(); try { Map<String, List<String>> headers = cookieHandler.get(documentBase.toURI(), new HashMap<String, List<String>>()); List<String> cookieHeaderValues = headers.get("Cookie"); if (null == cookieHeaderValues || cookieHeaderValues.isEmpty()) { addDetailMessage("ERROR: no session cookie detected!"); } else { /* * Of course we don't print out the session cookie... */ addDetailMessage("session cookie detected"); } } catch (Exception e) { addDetailMessage("error getting cookies from default cookie handler"); } } } public void addDetailMessage(String detailMessage) { this.view.addDetailMessage(detailMessage); } private class PcscEidObserver implements Observer { public void update(Observable observable, Object arg) { Controller.this.view.increaseProgress(); } } private FinishedMessage performEidIdentificationOperation(boolean includeAddress, boolean includePhoto, boolean includeIntegrityData, boolean includeCertificates, boolean removeCard, String identityDataUsage) throws Exception { waitForEIdCardPcsc(); setStatusMessage(Status.NORMAL, MESSAGE_ID.READING_IDENTITY); boolean response = this.view.privacyQuestion(includeAddress, includePhoto, identityDataUsage); if (false == response) { this.pcscEidSpi.close(); if (false == this.runtime.gotoCancelPage()) { throw new SecurityException("user did not agree to release eID identity information"); } else { return new FinishedMessage(ErrorCode.USER_CANCELED); } } addDetailMessage("Reading identity file..."); /* * Calculate the maximum progress bar indication */ int maxProgress = 1; // identity file if (includeAddress) { maxProgress++; } if (includePhoto) { maxProgress += 3000 / 255; } if (includeIntegrityData) { maxProgress++; // identity signature file if (includeAddress) { maxProgress++; // address signature file } maxProgress += (1050 / 255) + 1; // RRN certificate file maxProgress += (1050 / 255) + 1; // Root certificate file } if (includeCertificates) { maxProgress += (1050 / 255) + 1; // authn cert file maxProgress += (1050 / 255) + 1; // sign cert file maxProgress += (1050 / 255) + 1; // citizen CA cert file if (false == includeIntegrityData) { maxProgress += (1050 / 255) + 1; // root CA cert file } } this.view.resetProgress(maxProgress); TaskRunner taskRunner = new TaskRunner(this.pcscEidSpi, this.view); /* * Next design pattern is the only way to handle the case where multiple * application access the smart card at the same time. */ byte[] idFile = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.IDENTITY_FILE_ID); } }); addDetailMessage("Size identity file: " + idFile.length); byte[] addressFile = null; if (includeAddress) { addDetailMessage("Read address file..."); addressFile = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.ADDRESS_FILE_ID); } }); addDetailMessage("Size address file: " + addressFile.length); } byte[] photoFile = null; if (includePhoto) { addDetailMessage("Read photo file..."); photoFile = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.PHOTO_FILE_ID); } }); } byte[] identitySignatureFile = null; byte[] addressSignatureFile = null; byte[] rrnCertFile = null; byte[] rootCertFile = null; if (includeIntegrityData) { addDetailMessage("Read identity signature file..."); identitySignatureFile = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.IDENTITY_SIGN_FILE_ID); } }); if (includeAddress) { addDetailMessage("Read address signature file..."); addressSignatureFile = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.ADDRESS_SIGN_FILE_ID); } }); } addDetailMessage("Read national registry certificate file..."); rrnCertFile = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.RRN_CERT_FILE_ID); } }); addDetailMessage("size RRN cert file: " + rrnCertFile.length); addDetailMessage("reading root certificate file..."); rootCertFile = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.ROOT_CERT_FILE_ID); } }); addDetailMessage("size Root CA cert file: " + rootCertFile.length); } byte[] authnCertFile = null; byte[] signCertFile = null; byte[] caCertFile = null; if (includeCertificates) { addDetailMessage("reading authn certificate file..."); authnCertFile = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.AUTHN_CERT_FILE_ID); } }); addDetailMessage("size authn cert file: " + authnCertFile.length); addDetailMessage("reading sign certificate file..."); signCertFile = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.SIGN_CERT_FILE_ID); } }); addDetailMessage("size non-repud cert file: " + signCertFile.length); addDetailMessage("reading citizen CA certificate file..."); caCertFile = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.CA_CERT_FILE_ID); } }); addDetailMessage("size Cit CA cert file: " + caCertFile.length); if (null == rootCertFile) { addDetailMessage("reading root certificate file..."); rootCertFile = taskRunner.run(new Task<byte[]>() { public byte[] run() throws Exception { return Controller.this.pcscEidSpi.readFile(PcscEid.ROOT_CERT_FILE_ID); } }); addDetailMessage("size Root CA cert file: " + rootCertFile.length); } } this.view.setProgressIndeterminate(); if (removeCard) { setStatusMessage(Status.NORMAL, MESSAGE_ID.REMOVE_CARD); this.pcscEidSpi.removeCard(); } this.pcscEidSpi.close(); setStatusMessage(Status.NORMAL, MESSAGE_ID.TRANSMITTING_IDENTITY); IdentityDataMessage identityData = new IdentityDataMessage(idFile, addressFile, photoFile, identitySignatureFile, addressSignatureFile, rrnCertFile, rootCertFile, authnCertFile, signCertFile, caCertFile); FinishedMessage finishedMessage = sendMessage(identityData, FinishedMessage.class); return finishedMessage; } private void waitForEIdCardPcsc() throws Exception { setStatusMessage(Status.NORMAL, MESSAGE_ID.DETECTING_CARD); if (false == this.pcscEidSpi.hasCardReader()) { setStatusMessage(Status.NORMAL, MESSAGE_ID.CONNECT_READER); this.pcscEidSpi.waitForCardReader(); } if (false == this.pcscEidSpi.isEidPresent()) { setStatusMessage(Status.NORMAL, MESSAGE_ID.INSERT_CARD_QUESTION); this.pcscEidSpi.waitForEidPresent(); } } public static final String APPLET_SERVICE_PARAM = "AppletService"; private HttpURLConnection getServerConnection() throws MalformedURLException, IOException { String appletServiceParam = this.runtime.getParameter(APPLET_SERVICE_PARAM); if (null == appletServiceParam) { throw new IllegalArgumentException("no " + APPLET_SERVICE_PARAM + " parameter specified"); } URL appletServiceUrl = new URL(this.runtime.getDocumentBase(), appletServiceParam); /* * Install our SSL socket factory. */ AppletSSLSocketFactory.installSocketFactory(this.view); HttpURLConnection connection = (HttpURLConnection) appletServiceUrl.openConnection(); return connection; } private void setStatusMessage(Status status, Messages.MESSAGE_ID messageId) { this.view.setStatusMessage(status, messageId); } public Component getParentComponent() { return this.view.getParentComponent(); } }